/*
 * @Descripttion : 数字自增动画
 * @Versions     : 0.1
 * @Author       : foxui team
 * @Date         : 2022-03-16 23:04:08
 * @LastEditors  : Please set LastEditors
 * @LastEditTime : 2023-05-09 17:21:30
 */
/**
 * @description: 构造函数
 * @param {*} target       目标元素的id  *必填
 * @param {*} startVal     开始的值(从哪个值开始)  *必填
 * @param {*} endVal       结束的值(滚动到哪个值结束)  *必填
 * @param {*} decimals     小数位数，默认值为0  *可选
 * @param {*} duration     动画持续时间，单位为秒，默认值为2  *可选
 * @param {*} options      选项的可选对象  *可选
                {
                    useEasing: true,        --是否使用缓动动画，默认为缓动，可设置为false让其匀速
                    useGrouping: true,      --对数字进行分组，如12345，按三位一组变为类似12,345这样的
                    separator: ',',         --分组时使用的分隔符默认是逗号
                    decimal: '.',           --小数点
                    prefix: '',             --添加前缀如12345，变为￥12345
                    suffix: ''              --添加后缀如12345 通过添加后缀变为12345$，12345元之类的
                }
 * @method {*} pauseResume                  --> 暂停/恢复
 * @method {*} reset                        --> 重置动画  
 * @method {*} update(newVal)               --> 更新值  
 * @method {*} start(function(){})          --> 开始（实例化后执行）   
 * @return {*}
 * @Date: 2022-03-16 23:05:29
 */
function CountUp(target, startVal, endVal, decimals, duration, options) {
    var self = this;
    self.version = function () {
        return '1.9.2';
    };
    self.options = {
        useEasing: true,
        useGrouping: true,
        separator: ',',
        decimal: '.',
        easingFn: easeOutExpo,
        formattingFn: formatNumber,
        prefix: '',
        suffix: '',
        numerals: [],
    };
    if (options && typeof options === 'object') {
        for (var key in self.options) {
            if (options.hasOwnProperty(key) && options[key] !== null) {
                self.options[key] = options[key];
            }
        }
    }
    if (self.options.separator === '') {
        self.options.useGrouping = false;
    } else {
        self.options.separator = '' + self.options.separator;
    }
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
    }
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function (callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function () {
                callback(currTime + timeToCall);
            }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    }
    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
    }
    function formatNumber(num) {
        num = num.toFixed(self.decimals);
        num += '';
        var x, x1, x2, x3, i, l;
        x = num.split('.');
        x1 = x[0];
        x2 = x.length > 1 ? self.options.decimal + x[1] : '';
        if (self.options.useGrouping) {
            x3 = '';
            for (i = 0, l = x1.length; i < l; ++i) {
                if (i !== 0 && i % 3 === 0) {
                    x3 = '<b>' + self.options.separator + '</b>' + x3;
                }
                x3 = '<span>' + x1[l - i - 1] + '</span>' + x3;
            }
            x1 = x3;
        }
        if (self.options.numerals.length) {
            x1 = x1.replace(/[0-9]/g, function (w) {
                return self.options.numerals[+w];
            });
            x2 = x2.replace(/[0-9]/g, function (w) {
                return self.options.numerals[+w];
            });
        }
        var x2Arr = x2.split('');
        var _x2 = '';
        for (let i = 0; i < x2Arr.length; i++) {
            if (i == 0) {
                _x2 += '<b>' + x2Arr[i] + '</b>';
            } else {
                _x2 += '<span>' + x2Arr[i] + '</span>';
            }
        }
        return '<mark>' + self.options.prefix + '</mark>' + '<i></i>' + x1 + _x2 + '<mark>' + self.options.suffix + '</mark>';
    }

    function easeOutExpo(t, b, c, d) {
        return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
    }

    function ensureNumber(n) {
        return typeof n === 'number' && !isNaN(n);
    }
    self.initialize = function () {
        if (self.initialized) {
            return true;
        }
        self.error = '';
        self.d = typeof target === 'string' ? document.getElementById(target) : target;
        if (!self.d) {
            self.error = '[CountUp] target is null or undefined';
            return false;
        }
        self.startVal = Number(startVal);
        self.endVal = Number(endVal);
        if (ensureNumber(self.startVal) && ensureNumber(self.endVal)) {
            self.decimals = Math.max(0, decimals || 0);
            self.dec = Math.pow(10, self.decimals);
            self.duration = Number(duration) * 1000 || 2000;
            self.countDown = self.startVal > self.endVal;
            self.frameVal = self.startVal;
            self.initialized = true;
            return true;
        } else {
            self.error = '[CountUp] startVal (' + startVal + ') or endVal (' + endVal + ') is not a number';
            return false;
        }
    };
    self.printValue = function (value) {
        var result = self.options.formattingFn(value);
        if (self.d.tagName === 'INPUT') {
            this.d.value = result;
        } else {
            if (self.d.tagName === 'text' || self.d.tagName === 'tspan') {
                this.d.textContent = result;
            } else {
                this.d.innerHTML = result;
            }
        }
    };
    self.count = function (timestamp) {
        if (!self.startTime) {
            self.startTime = timestamp;
        }
        self.timestamp = timestamp;
        var progress = timestamp - self.startTime;
        self.remaining = self.duration - progress;
        if (self.options.useEasing) {
            if (self.countDown) {
                self.frameVal = self.startVal - self.options.easingFn(progress, 0, self.startVal - self.endVal, self.duration);
            } else {
                self.frameVal = self.options.easingFn(progress, self.startVal, self.endVal - self.startVal, self.duration);
            }
        } else {
            if (self.countDown) {
                self.frameVal = self.startVal - (self.startVal - self.endVal) * (progress / self.duration);
            } else {
                self.frameVal = self.startVal + (self.endVal - self.startVal) * (progress / self.duration);
            }
        }
        if (self.countDown) {
            self.frameVal = self.frameVal < self.endVal ? self.endVal : self.frameVal;
        } else {
            self.frameVal = self.frameVal > self.endVal ? self.endVal : self.frameVal;
        }
        self.frameVal = Math.round(self.frameVal * self.dec) / self.dec;
        self.printValue(self.frameVal);
        if (progress < self.duration) {
            self.rAF = requestAnimationFrame(self.count);
        } else {
            if (self.callback) {
                self.callback();
            }
        }
    };
    self.start = function (callback) {
        if (!self.initialize()) {
            return;
        }
        self.callback = callback;
        self.rAF = requestAnimationFrame(self.count);
    };
    self.pauseResume = function () {
        if (!self.paused) {
            self.paused = true;
            cancelAnimationFrame(self.rAF);
        } else {
            self.paused = false;
            delete self.startTime;
            self.duration = self.remaining;
            self.startVal = self.frameVal;
            requestAnimationFrame(self.count);
        }
    };
    self.reset = function () {
        self.paused = false;
        delete self.startTime;
        self.initialized = false;
        if (self.initialize()) {
            cancelAnimationFrame(self.rAF);
            self.printValue(self.startVal);
        }
    };
    self.update = function (newEndVal) {
        if (!self.initialize()) {
            return;
        }
        newEndVal = Number(newEndVal);
        if (!ensureNumber(newEndVal)) {
            self.error = '[CountUp] update() - new endVal is not a number: ' + newEndVal;
            return;
        }
        self.error = '';
        if (newEndVal === self.frameVal) {
            return;
        }
        cancelAnimationFrame(self.rAF);
        self.paused = false;
        delete self.startTime;
        self.startVal = self.frameVal;
        self.endVal = newEndVal;
        self.countDown = self.startVal > self.endVal;
        self.rAF = requestAnimationFrame(self.count);
    };
    if (self.initialize()) {
        self.printValue(self.startVal);
    }
}

/* 绑定 构造函数
 *********************************************************************************************************************/
/**
 * @description: 绑定元素滚动至可视区域执行
 * @param {*} id
 * @param {*} startVal
 * @param {*} endVal
 * @param {*} decimals
 * @param {*} duration
 * @param {*} options
 * @param {*} headerId
 * @param {*} callback
 * @return {*}
 * @Date: 2022-03-17 10:54:24
 */
function BindCountup(id, startVal, endVal, decimals, duration, options, headerId, callback) {
    this.id = id;
    this.startVal = startVal;
    this.endVal = endVal;
    this.decimals = decimals;
    this.duration = duration;
    this.options = options;
    this.headerId = headerId;
    this.callback = callback;
    this.isRuned = false;
    this.countup = null;
}

BindCountup.prototype._init = function () {
    this._bindScroll();
};

BindCountup.prototype._bindScroll = function () {
    $(document).on('scroll', () => {
        let sTop = $(window).scrollTop(), //滚动条距离顶端的高度
            vHeight = document.documentElement.clientHeight, //浏览器可视区域高度
            headerHeight = $('#' + this.headerId).height() || 0; //页面表头的高度

        let bottomTop = sTop + vHeight - headerHeight, // 可视区域底部 scrollTop
            topTop = sTop + headerHeight; // 可视区域顶部 scrollTop

        let targetTop = $('#' + this.id).offset().top,
            targetBottom = $('#' + this.id).height() + targetTop;

        if (!(topTop > targetBottom || bottomTop < targetTop) && !this.isRuned) {
            let countup = new CountUp(this.id, this.startVal, this.endVal, this.decimals, this.duration, this.options);
            countup.start(this.callback);
            this.countup = countup;
            this.isRuned = true;
        }
    });
};

/* 实例化
 *********************************************************************************************************************/
/**
 * @description: 数字自增动画
 * @param {*} id                目标元素的id  *必填
 * @param {*} startVal          开始的值(从哪个值开始)  *必填
 * @param {*} endVal            结束的值(滚动到哪个值结束)  *必填
 * @param {*} decimals          小数位数，默认值为0  *可选
 * @param {*} duration          动画持续时间，单位为秒，默认值为2  *可选
 * @param {*} options           选项的可选对象  *可选
 * @param {*} scroll            是否滚动至元素才执行，默认值false *可选
 * @param {*} headerId          可视区域浮动头部元素Id
 * @param {*} callback          回调函数 *可选
 * @return {*}
 * @Date: 2022-03-17 08:36:48
 */
function countup({ id, startVal, endVal, decimals, duration, options, scroll, headerId, callback }) {
    options = options || { useEasing: true, useGrouping: true, separator: '', decimal: '.', prefix: '', suffix: '' };
    callback = callback || function () {};
    scroll = scroll === true ? scroll : false;

    if (scroll) {
        let bindCountup = new BindCountup(id, startVal, endVal, decimals, duration, options, headerId, callback);
        bindCountup._init();
        return bindCountup;
    } else {
        let countup = new CountUp(id, startVal, endVal, decimals, duration, options);
        countup.start(callback);
        return countup;
    }
}

export { countup };
